<?php
require_once('php/lib/db/database.php');

/**
 * Sqlite database.
 *
 * Configuration settings: 
 *	- config['file'] = filename
 *	- config['mode'] = creation mode
 *
 * Optional configuration settings:
 *  - config['persistent'] = indicator (true/false) if persistent connections should be used (defaults to false)
 *
 * @author Pascal de Vink
 */
class SqliteDatabase extends Database {

	/**
	 * Database connection.
	 *
	 * @var handle
	 * @access private
	 */
	var $_connection = NULL;
	
	/**
	 * Database name.
	 *
	 * @var string
	 * @access private
	 */
	var $_database = '';
	
	/** 
	 * @see Database.open
	 */
	function open() {
		if ($this->_begin('open', '', false) == false) return false;
		
		# get database properties
		$file = $this->config['file'];
		$mode = $this->config('mode', '0666');
		$persistent = $this->config('persistent', false);

		if (!$file) trigger_error('No database file specified');
		
		# check if database exists
		if ($file != ':memory:' && !file_exists($file)) {
			return $this->_fail("Database $file doesn't exist");
		}

		# open database
		$error = '';
		if ($persistent) {
			$this->_connection = sqlite_popen($file, $mode, $error);
		} else {
			$this->_connection = sqlite_open($file, $mode, $error);
		}
		
		return $this->_connection? $this->_end() : $this->_fail($error);
	}

	/**
	 * @see Database.create
	 */
	function create() {
		if ($this->_begin('create', '', false) == false) return false;

		# get database properties
		$file = $this->config['file'];
		$mode = $this->config('mode', '0666');
		$persistent = $this->config('persistent', false);
		
		if (!$file) trigger_error('No database file specified');
		
		# check if database not already exists
		if ($file != ':memory:' && file_exists($file)) {
			return $this->_fail("Database $file already exists");
		}

		# create database
		$error = '';
		if ($persistent) {
			$this->_connection = @sqlite_popen($file, $mode, $error);
		} else {
			$this->_connection = @sqlite_open($file, $mode, $error);
		}

		return $this->_connection? $this->_end() : $this->_fail($error);
	}
	
	/** 
	 * @see Database.drop
	 */
	function drop() {
		if ($this->_begin('drop', '', false) == false) return false;

		# get database properties
		$file = $this->config('file');
		
		if (!$file) trigger_error('No database file specified');
		if ($file == ':memory:') return true;
		
		# check if database exists
		if (!file_exists($file)) {
			return $this->_fail(false, "Database $file does not exist");
		}

		# check if database opened
		if ($this->_connection) {
		    sqlite_close($this->_connection);
			$this->_connection = NULL;
		}
		
		# remove database file
		if (!unlink($file)) {
			return $this->_fail(false, "Database $file could not be deleted");
		}
		
		return $this->_end();
	}
	
	/** 
	 * @see Database.close 
	 */
	function close() {
		if ($this->_begin('close', '') == false) return false;

		# close database connection
	    sqlite_close($this->_connection);
		$this->_connection = NULL;
		
		return $this->_end();
	}

	/**
	 * @see Database.isOpen
	 */
	function isOpen() {
		return $this->_connection;
	}

	/**
	 * @see Database.query 
	 */
	function &query($query, $limit = NULL, $offset = NULL) {
		if ($this->_begin('query', $query) == false) return false;
		
		# add limit clause
		if (!is_null($limit)) {
			$query .= ' ' . $this->_limitClause($limit, $offset);
		}

		# perform query
		$handle = sqlite_query($query, $this->_connection);
		if (!$handle) {
			return $this->_fail();
		}
		
		$this->_end();
		return new SqliteResultSet($this, $handle);		
	}
	
	/**
	 * @see Database.queryFirst
	 */
	function &queryFirst($query, $offset = NULL) {
		if ($this->_begin('queryFirst', $query) == false) return false;

		# add limit to query
		$query .= ' ' . $this->_limitClause(1, $offset);

		# perform query
		$handle = sqlite_query($query, $this->_connection);
		if (!$handle) {
			return $this->_fail();
		}

		# fetch first record
		$row = sqlite_fetch_array($handle, SQLITE_ASSOC);
		
		$this->_end();
		return $row;
	}

	/** 
	 * @see Database.queryAll
	 */
	function &queryAll($query, $limit = NULL, $offset = NULL) {
		if ($this->_begin('all', $query) == false) return false;

		# add limit to query
		if (!is_null($limit)) {
			$query .= ' ' . $this->_limitClause($limit, $offset);
		}
		
		# perform query
		$handle = sqlite_query($query, $this->_connection);
		if (!$handle) {
			return $this->_fail();
		}

		# fetch records
		$rows =& sqlite_fetch_all($handle, SQLITE_ASSOC);

		$this->_end();
		return $rows;
	}
	
	/**
	 * @see Database.execute
	 */
	function execute($command) {
		if ($this->_begin('execute', $command) == false) return false;
		
		# perform query
		if (!sqlite_exec($command, $this->_connection)) {
			return $this->_fail();
		}
		
		return $this->_end(sqlite_changes($this->_connection));
	}

	/**
	 * @see Database.begin
	 */
	function begin() {
		if ($this->_begin('begin') == false) return false;
		
		# start transaction
		if (!sqlite_exec("BEGIN", $this->_connection)) {
			return $this->_fail();
		}
		
		return $this->_end();
	}
	
	/**
	 * @see Database.rollback
	 */
	function rollback() {
		if ($this->_begin('rollback') == false) return false;
		
		# rollback transaction
		if (!sqlite_exec("ROLLBACK", $this->_connection)) {
			return $this->_fail();
		}
		
		return $this->_end();
	}
	
	/**
	 * @see Database.commit
	 */
	function commit() {
		if ($this->_begin('commit') == false) return false;
		
		# commit transaction
		if (!sqlite_exec("COMMIT", $this->_connection)) {
			return $this->_fail();
		}
		
		return $this->_end();
	}
	
	/** 
	 * @see Database.tables 
	 */
	function tables() {
		if ($this->_begin('tables') == false) return false;

		# retrieve tables
		$handle = sqlite_query($this->_connection, "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name");
		if (!$handle) {
			return $this->_fail();
		}

		# create list of tables
		$tables = array();
		while (($row = sqlite_fetch_array($handle, SQLITE_NUM))) {
			$tables[] = array('name' => $row[0]);
		}
		
		$this->_end();
		return $tables;
	}

	/** 
	 * @see Database.fields 
	 */
	function fields($table_name) {
		if ($this->_begin('fields', "table $table") == false) return false;

		# retrieve table fields
		$columns = sqlite_fetch_column_types($table_name, $this->_connection);
		if (!$handle) {
			return $this->_fail();
		}

		# create list of fields
		$fields = array();
      	foreach ($columns as $column => $type) {
	        $fields[] = array('name' => $column, 'type' => $type);
      	}

		$this->_end();
		return $fields;
	}

	/**
	 * @see Database.lastInsertId 
	 */
	function lastInsertId() {
		return sqlite_last_insert_rowid($this->_connection);
	}

	/**
	 * @see Database.lastAffected 
	 */
	function lastAffected() {
		return sqlite_changes($this->_connection);
	}

	/** 
	 * @see Database.encode 
	 */
	function encode($value) {
		if (is_null($value) || ($this->config('empty_as_null', true) && empty($value))) {
			return 'NULL';
		} else if (get_magic_quotes_gpc()) {
	   		return "'" . sqlite_escape_string(stripslashes($value)) . "'";
   		} else {
	   		return "'" . sqlite_escape_string($value) . "'";
   		}
	}
	
	/**
	 * Returns last occured error
	 *
	 * @return string Last error
	 * @access private
	 */
	function _error() {
		return $this->_connection? sqlite_error_string(sqlite_last_error($this->_connection)) : 'Unknown connection error occured';
	}
	
	/**
	 * Returns limit clause for use in an SQL statement.
	 *
	 * @param int $limit Optional limit of results returned
	 * @param int $offset Optional offset from which to start results (starts at 0)
	 * @return string Limit clause
	 * @access private
	 */
	function _limitClause($limit = NULL, $offset = NULL) {
		return (!is_null($limit)? 'LIMIT ' . $limit . (!is_null($offset)? ' OFFSET ' . $offset : '') : '');
	}	
}

/** @access private */
class SqliteResultSet extends ResultSet {
	
	/**
	 * Handle for resultset
	 *
	 * @var handle
	 * @access private
	 */
	var $_handle = NULL;
	
	/**
	 * Constructs new instance
	 */
	function __construct(&$database, $handle) {
		parent::__construct($database);
		$this->_handle = $handle;
	}

	/**
	 * @see ResultSet.next
	 */
	function &next() {
		return sqlite_fetch_array($this->_handle, SQLITE_ASSOC);
	}
	
	/**
	 * @see ResultSet.resets
	 */
	function reset() {
		return sqlite_rewind($this->_handle);
	}	

	/**
	 * @see ResultSet.count
	 */
	function count() {
		return sqlite_num_rows($this->_handle);
	}
	
	/**
	 * @see ResultSet.isEmpty
	 */
	function isEmpty() {
		return sqlite_num_rows($this->_handle) == 0;
	}	
	
	/**
	 * @see Database.fields
	 */
	function fields() {
		$count = sqlite_num_fields($this->_handle);

		$fields = array();
		for ($i = 0; $i < $count; $i++) {
			$fields[] = array('name' => sqlite_field_name($this->_handle, $i));
		}
		return $fields;
	}

	/**
	 * @see ResultSet.fieldCount
	 */
	function fieldCount() {
		return sqlite_num_fields($this->_handle);
	}
}
?>